#! python
#! -*- coding: utf-8 -*-

__author__ = 'Anton Vakhrushev'

import os
import yafotki
import photo
from lib.extra import utils


class AlbumError(Exception):
    pass


class Album(object):
    def __init__(self):
        self.parent = None
        self.children = []

    @property
    def id(self):
        return None

    @property
    def count(self):
        return None

    @property
    def link_self(self):
        return None

    def is_web(self):
        return True

    def append_child(self, album):
        album.parent = self
        self.children.append(album)


class ServiceAlbum(Album):
    def __init__(self, yandex_service, settings):
        """
        @type yandex_service: yafotki.YandexService
        """
        Album.__init__(self)
        self.service = yandex_service
        self.settings = settings
        self._build_album_tree(self.service.albums, settings, self, 0, '')

    @property
    def title(self):
        return u'Все фотографии'

    @property
    def link_self(self):
        return self.service.link_self

    def get_photos(self, progress):
        map_f = lambda x: photo.MatchedPhoto(remote=x)
        msg_f = lambda index, item: u'Фотография: {}'.format(item.title)
        return list(progress.map(map_f, self.service.iter_photos(100), title=u'Загружаем фотографии', message=msg_f))

    def create_album(self, title, summary=None):
        new_yandex_album = self.service.create_album(title, summary)
        new_photo_album = PhotoAlbum(new_yandex_album)
        self.append_child(new_photo_album)
        return new_photo_album

    @staticmethod
    def _build_album_tree(yaalbums, settings, current, level, pattern):
        """
        Построить дерево альбомов на основе альбомов на сервисе с учетом альбомов,
        которые необходимо синхронизировать.
        Это очень важная функция! Она синхронизирует иерархию альбомов.
        @type yaalbums: list of YandexAlbum
        @type settings: dict
        @type current: ServiceAlbum|PhotoAlbum
        @type level: int
        @type pattern: basestring
        """
        f = lambda i: i.link_parent == current.link_self if current.is_web() else False
        web_albums = [PhotoAlbum(i, settings.get(i.id, {})) for i in yaalbums if f(i)]
        local_albums = []

        if isinstance(current, PhotoAlbum):
            # если есть собственные настройки синхронизации
            if current.sync:
                level = current.deep
                pattern = current.pattern
            # да, синхронизоровать надо
            if current.folder and pattern and level > 0:
                for folder_name in os.listdir(current.folder):
                    folder_path = os.path.join(current.folder, folder_name)
                    if not os.path.isdir(folder_path):
                        continue
                    if not utils.check_files(folder_path, pattern):
                        continue
                    try:
                        web_album = next((i for i in web_albums if i.title == folder_name))
                        web_album.folder = folder_path
                        web_album.pattern = pattern
                    except StopIteration:
                        local_album = PhotoAlbum(None)
                        local_album.folder = folder_path
                        local_album.pattern = pattern
                        local_albums.append(local_album)

        for album in web_albums + local_albums:
            new_level = level - 1 if level > 0 else 0
            ServiceAlbum._build_album_tree(yaalbums, settings, album, new_level, pattern)
            current.append_child(album)


class PhotoAlbum(Album):

    def __init__(self, yaalbum, settings=None):
        """
        @type album: yafotki.YandexAlbum|None
        @type settings: dict|None
        """
        Album.__init__(self)
        self.yaalbum = yaalbum
        """@type : yafotki.YandexAlbum|None"""
        # settings
        self.folder = None
        self.sync = False
        self.pattern = None
        self.deep = 0
        # apply settings
        if settings:
            self.set_settings(settings)
        self.remote_photos = None
        self.local_photos = None

    def is_web(self):
        return bool(self.yaalbum)

    def is_local(self):
        return bool(self.folder)

    def is_matched(self):
        return self.is_web() and self.is_local()

    def is_synced(self):
        return self.sync

    def is_edited(self):
        if not self.is_matched():
            return False
        remote_mtime = self.yaalbum.published
        local_mtime = utils.get_mtime(self.folder, self.pattern)
        return local_mtime > remote_mtime

    @property
    def id(self):
        return self.yaalbum.id if self.yaalbum else self.folder

    @property
    def title(self):
        if self.yaalbum:
            return self.yaalbum.title
        if self.folder:
            return os.path.split(self.folder)[1]
        return ''

    @property
    def link_self(self):
        return self.yaalbum.link_self

    def count_local(self):
        if self.folder and self.pattern:
            return utils.count_files(self.folder, self.pattern)
        return 0

    def count_remote(self):
        return self.yaalbum.count if self.yaalbum else 0

    def set_settings(self, settings):
        self.sync = settings.get('sync', False)
        self.folder = settings.get('folder')
        self.pattern = settings.get('pattern')
        self.deep = settings.get('deep', 0)

    def get_settings(self):
        return {
            'sync': self.sync,
            'folder': self.folder,
            'pattern': self.pattern,
            'deep': self.deep,
        }

    def refresh(self, progress):
        f = lambda: [photo.MatchedPhoto(remote=yaphoto) for yaphoto in self.yaalbum.photos] if self.yaalbum else []
        web_photos = progress.do(f, title=u'Загрузка альбома')

        local_paths = []
        if self.folder and self.pattern:
            local_paths = list(utils.find_files(self.folder, self.pattern))

        map_f = lambda x: photo.MatchedPhoto(local=x)
        msg_f = lambda index, item: u'Файл: {}'.format(item)
        local_photos = list(progress.map(map_f, local_paths, title=u'Загружаем файлы', message=msg_f))

        if web_photos and local_photos:
            non_sync = []
            msg_f = lambda index, item: u'Файл: {}'.format(item.title)
            for local_photo in progress.wrap(local_photos, title=u'Ищем соответствия', message=msg_f):
                f = lambda web_photo: web_photo.similar_to(local_photo)
                most_proper = min(web_photos, key=f)
                if most_proper.similar_to(local_photo) <= 10.0:
                    most_proper.assign_local(local_photo)
                else:
                    non_sync.append(local_photo)
            local_photos = non_sync

        self.local_photos = local_photos
        self.remote_photos = web_photos

    def get_remote_photos(self, progress):
        if self.remote_photos is None:
            self.refresh(progress)
        return self.remote_photos

    def get_local_photos(self, progress):
        if self.local_photos is None:
            self.refresh(progress)
        return self.local_photos

    def get_photos(self, progress):
        return self.get_remote_photos(progress) + self.get_local_photos(progress)

    def create_album(self, title, summary=None):
        """@rtype : PhotoAlbum"""
        if not self.yaalbum:
            raise AlbumError(u'Нет Yandex-альбома')
        if self.yaalbum.count:
            raise AlbumError(u'Нельзя создать альбом в непустом альбоме')
        new_yandex_album = self.yaalbum.create_album(title, summary)
        new_photo_album = PhotoAlbum(new_yandex_album)
        self.append_child(new_photo_album)
        return new_photo_album

    def delete(self, progress):
        if not self.yaalbum:
            raise AlbumError(u'Нет Yandex-альбома')
        self.yaalbum.delete()
        self.yaalbum = None
        self.refresh(progress)

    def upload(self, matched_photo):
        """
        Загружает фотографию в альбом. Фотография должна быть локальной
        @type photo: photo.MatchedPhoto
        @raise AlbumError
        """
        if not self.yaalbum:
            raise AlbumError(u'Нет Yandex-альбома')
        if not matched_photo.is_local(strict=True):
            raise AlbumError(u'Это не локальный файл')
        path = matched_photo.data.get_path()
        yaphoto = self.yaalbum.upload(path)
        yaphoto.edit(access='public')
        print 'published', yaphoto.published
        print 'edited   ', yaphoto.edited
        # matched_photo.web = yaphoto

    def synchronize(self, progress):
        if not self.is_matched() and self.parent:
            new_album = self.parent.create_album(self.title)
            self.yaalbum = new_album.yaalbum
        self.refresh(progress)
        local_photos = self.get_local_photos(progress)
        msg_f = lambda i, x: u'{} ({} MB)'.format(x.title, round(x.size / 1024.0 / 1024.0))
        for ph in progress.wrap(local_photos, title=u'Загружаем фотографии', message=msg_f):
            self.upload(ph)
        self.refresh(progress)
